﻿Imports System.Reflection
Imports System.Text.RegularExpressions

Public NotInheritable Class NormalColumnAttributeFactory

    Private Shared _factory As NormalColumnAttributeFactory = Nothing

    Public Shared Function Instance() As NormalColumnAttributeFactory
        '#97　MySQL対応
        'DBMSごとの生成クラスを統合
        If _factory Is Nothing Then
            _factory = New NormalColumnAttributeFactory
        End If
        Return _factory
    End Function

    ''' <summary>
    ''' 型にあるプロパティから列属性を生成します。
    ''' </summary>
    ''' <param name="atr"></param>
    ''' <remarks></remarks>
    Public Function Create(ByVal atr As TableAttribute) As List(Of ColumnAttribute)
        Dim cols As New List(Of ColumnAttribute)

        '型がルート、または、独自のプロパティ、または、主キーがターゲット
        Dim fn As Predicate(Of PropertyInfo) =
            Function(prop)
                If atr.IsRootBase Then Return True
                If prop.DeclaringType.Equals(atr.ModelType) Then Return True
                If (From el In ColumnAttributeReader.Read(prop) Where el.IsPrimaryKey).Any Then Return True
                Return False
            End Function

        '通常列
        For Each item In (From el In atr.ModelType.GetProperties Where fn.Invoke(el) AndAlso PropertyReader.IsColumn(el) AndAlso PropertyReader.IsParent(el) = False)
            '#60　オーバーライド
            If atr.IsRootBase = False AndAlso item.GetGetMethod.IsVirtual AndAlso item.GetGetMethod.IsFinal = False Then Continue For
            cols.Add(CreateMain(item))
        Next

        Return cols
    End Function

    ''' <summary>
    ''' 列属性を生成します
    ''' </summary>
    ''' <param name="prop"></param>
    ''' <returns></returns>
    ''' <remarks>
    ''' 指定がある場合はそちらが優先され、指定がない場合は自動生成します
    ''' </remarks>
    Private Function CreateMain(ByVal prop As PropertyInfo) As ColumnAttribute
        '指定属性
        Dim manual As ColumnAttribute = CreateAsManual(prop)

        If manual IsNot Nothing Then Return manual

        '制約上属性
        Dim master As ColumnAttribute = CreateAsMaster(prop)

        Return master
    End Function

    ''' <summary>
    ''' 指定された列属性を取得します
    ''' </summary>
    ''' <param name="prop"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function CreateAsManual(ByVal prop As PropertyInfo) As ColumnAttribute
        Dim atr As ColumnAttribute = AttributeReader.Read(Of ColumnAttribute)(prop).FirstOrDefault
        If atr Is Nothing Then Return Nothing

        '列名の自動補填
        If atr.ColumnName Is Nothing Then atr.ColumnName = prop.Name

        'プロパティの逆引き
        atr.PropertyInfo = prop

        '主キー
        atr.Key = KeyAttributeReader.Read(prop).FirstOrDefault

        Return atr
    End Function

    ''' <summary>
    ''' 制約上の列属性を生成します
    ''' </summary>
    ''' <param name="prop"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function CreateAsMaster(ByVal prop As PropertyInfo) As ColumnAttribute
        Dim atr As ColumnAttribute = Nothing

        Dim name As String = Me.GetColumnName(prop)

        If Regex.IsMatch(prop.Name, "^(CreateTimestamp|CreatedTimestamp)$") Then
            atr = New CreateTimestampAttribute(name)
        ElseIf Regex.IsMatch(prop.Name, "^(UpdateTimestamp|UpdatedTimestamp)$") Then
            atr = New UpdateTimestampAttribute(name)
        ElseIf Regex.IsMatch(prop.Name, "^(LockVersion|VersionKey)$") Then
            atr = New VersionKeyAttribute(name)
        Else
            atr = New ColumnAttribute(name)
        End If

        'プロパティの逆引き
        atr.PropertyInfo = prop

        '#62　ReadOnly, WriteOnly Propertyに対応する
        If prop.CanRead AndAlso prop.CanWrite = False Then
            atr.IsLoadable = False
        End If
        If prop.CanRead = False AndAlso prop.CanWrite Then
            atr.IsInsertable = False
            atr.IsUpdatable = False
        End If

        '主キー
        atr.Key = KeyAttributeReader.Read(prop).FirstOrDefault

        Return atr
    End Function

    ''' <summary>
    ''' 列名を取得します
    ''' </summary>
    ''' <param name="prop"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function GetColumnName(prop As PropertyInfo) As String
        Return Me.GetColumnName(prop.Name)
    End Function

    ''' <summary>
    ''' 列名を取得します
    ''' </summary>
    ''' <param name="val"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GetColumnName(val As String) As String
        '#97　MySQL対応
        Return StringFormConverter.Instance.ToColumn(val)
    End Function

End Class
